﻿using System;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.IO;
using System.Reflection;

namespace RuntimeCompiler
{
    //Cannot completely clean-up created dll after usage. Do it manually until solution is found.
    public class RuntimeCompiler : IDisposable
    {
        public enum Languages { [Description( "C#" )]C_SHARP, [Description( "VB" )] VISUAL_BASIC }

        public CompilerParameters CompilerParameters { get; set; }
        public CodeDomProvider CodeProvider { get; private set; }

        private Assembly compiledAssembly;
        private Assembly loadedAssembly;
        private AppDomain newDomain;

        private string CreateNewAssemblyFullPath()
        {
            string executingAssemblyPath = Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location );
            return Path.Combine( executingAssemblyPath, Guid.NewGuid().ToString() + ".dll" );
        }

        public RuntimeCompiler( Languages language )
        {
            string languageLiteral = language.GetEnumDescription();
            this.CodeProvider = CodeDomProvider.CreateProvider( languageLiteral );

            this.CompilerParameters = new CompilerParameters()
            {
                CompilerOptions = "/target:library /optimize",
                WarningLevel = 3,
                GenerateExecutable = false,
                GenerateInMemory = false,
            };
        }

        public Assembly Compile( string code )
        {
            this.CompilerParameters.OutputAssembly = this.CreateNewAssemblyFullPath();
            this.CompilerParameters.ReferencedAssemblies.Add( "System.dll" );
            this.CompilerParameters.ReferencedAssemblies.Add( "mscorlib.dll" );

            CompilerResults results = this.CodeProvider.CompileAssemblyFromSource( this.CompilerParameters, code );

            if( results.Errors.HasErrors )
                throw new CompilerException( results.Errors );

            return compiledAssembly = results.CompiledAssembly;
        }

        public dynamic CreateInstance( string fullyQualifiedClass )
        {
            this.newDomain = AppDomain.CreateDomain( Guid.NewGuid().ToString() );

            byte[] rawAssembly = File.ReadAllBytes( this.compiledAssembly.Location );
            this.loadedAssembly = this.newDomain.Load( rawAssembly, null );

            return loadedAssembly.CreateInstance( fullyQualifiedClass );
        }

        public void Dispose()
        {
            string location = this.compiledAssembly.Location;

            AppDomain.Unload( this.newDomain );
            this.newDomain = null;
        }
    }
}
